package com.pisolution.actuator.client.dependencies;

import org.apache.commons.lang3.StringUtils;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.Model;
import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
import org.springframework.core.io.ClassPathResource;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

/**
 * 用于检测客户端应用程序第三方依赖包使用情况.
 *
 * @author sora
 * @version 0.1.0
 */
public class DependencyDetector {
    /** 用于存储应用依赖包使用情况. */
    private final List<ApplicationDependency> dependencyList;

    // -------------------------------------------------------------------------------------------------
    //  初始化
    // -------------------------------------------------------------------------------------------------

    public DependencyDetector() {
        this.dependencyList = new ArrayList<>();
    }

    // -------------------------------------------------------------------------------------------------
    //  属性获取
    // -------------------------------------------------------------------------------------------------

    public List<ApplicationDependency> getDependencyList() {
        return new ArrayList<>(this.dependencyList);
    }

    // -------------------------------------------------------------------------------------------------
    //  检测检测
    // -------------------------------------------------------------------------------------------------

    /**
     * 读取类路径下所有的文件并尝试检测出其中的应用依赖信息.
     */
    public void detect() {
        try {
            // 1. 获取类路径下特定路径下的文件.
            final Enumeration<URL> urls = this.getClass().getClassLoader().getResources("META-INF");
            while (urls.hasMoreElements()) {
                final URL url = urls.nextElement();

                // 2. 依据文件协议类型过滤文件.
                if (url == null || !url.getProtocol().equals("jar")) {
                    continue;
                }

                // 3. 解析源文件.
                final String jarLocation = url.toString();
                this.readPomFile(jarLocation.substring(jarLocation.indexOf("f"), jarLocation.lastIndexOf("!")));
            }
        } catch (IOException ignore) {
            // 无需处理此处异常.
        }
    }

    /**
     * 读取给定路径下的应用描述文件.
     *
     * @param location 应用描述文件的路径.
     *
     * @throws IOException 如果文件读取失败.
     */
    private void readPomFile(final String location) throws IOException {
        // 1. 尝试读取源文件.
        try (ZipInputStream stream = new ZipInputStream(
            location.contains("!")
                ? new ClassPathResource(location.substring(location.indexOf("!") + 1)).getInputStream()
                : new URL(location).openStream()
        )) {
            ZipEntry entry;

            // 2. 寻找源文件中的应用依赖描述文件.
            while ((entry = stream.getNextEntry()) != null) {
                final String name = entry.getName();
                if (name.startsWith("META-INF/maven") && name.endsWith("pom.xml")) {
                    // 3. 解析应用描述文件.
                    this.parsePomFile(stream);
                }
            }
        }
    }

    /**
     * 从给定的文件输入流中解析出应用依赖包描述信息.
     *
     * @param stream 应用描述文件输入流.
     */
    private void parsePomFile(final InputStream stream) {
        // 1. 确认输入流是否有效.
        if (stream == null) {
            return;
        }

        // 2. 尝试解析应用描述文件.
        final MavenXpp3Reader reader = new MavenXpp3Reader();
        try {
            // 3. 解析出应用描述模型.
            final Model model = reader.read(stream);

            // 4. 读取应用依赖列表.
            final List<Dependency> dependencies = model.getDependencies();
            final Properties properties = model.getProperties();

            // 5. 解析依赖包.
            for (Dependency dependency: dependencies) {
                // 6. 解析依赖包组并忽略应用内部模块.
                final String groupId = dependency.getGroupId();
                if (groupId.equals("${project.groupId}")) {
                    continue;
                }

                final ApplicationDependency applicationDependency = new ApplicationDependency();

                // 7. 解析依赖包版本.
                String version = dependency.getVersion();
                if (StringUtils.isNotBlank(version) && version.contains("$")) {
                    version = properties.getProperty(version.substring(2, version.length() - 1));
                }

                if (StringUtils.isBlank(version)) {
                    continue;
                }

                applicationDependency.setGroup(groupId);
                applicationDependency.setArtifact(dependency.getArtifactId());
                applicationDependency.setVersion(version);

                this.dependencyList.add(applicationDependency);
            }
        } catch (XmlPullParserException | IOException ignore) {
            // 此异常无需处理.
        }
    }
}
